package tools

import (
	"context"
	"errors"
	"gozero-microservices/gateway-api/internal/svc"
	"sync"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

/*
 +----------------------------------------------------------------------
 + Title        : mongo
 + Author       : huwl
 + Version      : 1.0.0
 + Initial-Time : 2021-09-22
 + Last-time    : 这个文件最后修改的时间 + 修改人的名称
 + Desc         : mongo
 +----------------------------------------------------------------------
*/

var Ctx context.Context

var (
	mongoInstance *MongoInstance
	mongoOnce     sync.Once
	mongoInitErr  error
)

// Mongo 配置信息
type MongoConfig struct {
	Mode   string `yaml:"mode" json:"mode"`
	Single struct {
		Host string `yaml:"host" json:"host"`
	}
	Replication struct {
		Host string `yaml:"host" json:"host"`
	}
	Cluster struct {
		Host string `yaml:"host" json:"host"`
	}
}

// 定义Mongo
type MongoInstance struct {
	mongoClient *mongo.Client
}

/**
 * 创建mongo实例
 *
 * @return *MongoInstance, error
 * @author huwl
 */
func NewMongoInstance(svcCtx *svc.ServiceContext) (*MongoInstance, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	mongoConf := &MongoConfig{}

	// +++++++++++++++++ 使用本地配置方式开始 +++++++++++++++++
	mongoConf.Mode = svcCtx.Config.MongoConf.Mode

	switch svcCtx.Config.Env {
	case "dev":
		// 单机模式
		mongoConf.Single.Host = svcCtx.Config.MongoConf.Dev.Single.Host

		// 复制模式
		mongoConf.Replication.Host = svcCtx.Config.MongoConf.Dev.Replication.Host

		// 分片模式
		mongoConf.Cluster.Host = svcCtx.Config.MongoConf.Dev.Cluster.Host
	case "test":
		// 单机模式
		mongoConf.Single.Host = svcCtx.Config.MongoConf.Test.Single.Host

		// 复制模式
		mongoConf.Replication.Host = svcCtx.Config.MongoConf.Test.Replication.Host

		// 分片模式
		mongoConf.Cluster.Host = svcCtx.Config.MongoConf.Test.Cluster.Host
	case "prod":
		// 单机模式
		mongoConf.Single.Host = svcCtx.Config.MongoConf.Prod.Single.Host

		// 复制模式
		mongoConf.Replication.Host = svcCtx.Config.MongoConf.Prod.Replication.Host

		// 分片模式
		mongoConf.Cluster.Host = svcCtx.Config.MongoConf.Prod.Cluster.Host
	}
	// +++++++++++++++++ 使用本地配置方式结束 +++++++++++++++++

	// +++++++++++++++++ 使用配置中心方式开始 +++++++++++++++++
	// InitConfigCenter(MongoConfigCenter, mongoConf)
	// +++++++++++++++++ 使用配置中心方式结束 +++++++++++++++++

	var host string
	switch mongoConf.Mode {
	case "single":
		host = mongoConf.Single.Host
	case "replication":
		host = mongoConf.Replication.Host
	case "cluster":
		host = mongoConf.Cluster.Host
	}

	client, err := mongo.Connect(ctx, options.Client().ApplyURI(host))
	if err != nil {
		return nil, err
	}

	return &MongoInstance{
		mongoClient: client,
	}, nil
}

/**
 * 获取单列模式：mongo实例
 *
 * @return *MongoInstance, error
 * @author huwl
 */
func GetSingleMongoInstance(svcCtx *svc.ServiceContext) (*MongoInstance, error) {
	mongoOnce.Do(func() {
		mongoInstance, mongoInitErr = NewMongoInstance(svcCtx)
	})
	return mongoInstance, mongoInitErr
}

/**
 * 新增文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param map[string]string document ----------------必传
 * @return interface{}, error
 */
func (m *MongoInstance) AddDocument(database, collection string, document map[string]string) (interface{}, error) {
	if database == "" {
		return nil, errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return nil, errors.New("集合名称不能为空")
	}
	if len(document) == 0 {
		return nil, errors.New("文档数据不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	result, err := collect.InsertOne(Ctx, document)
	if err != nil {
		return nil, err
	}

	return result.InsertedID, err
}

/**
 * 批量新增文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param []interface{} documents -------------------必传
 * @return interface{}, error
 */
func (m *MongoInstance) BatchAddDocument(database, collection string, documents []interface{}) (interface{}, error) {
	if database == "" {
		return nil, errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return nil, errors.New("集合名称不能为空")
	}
	if len(documents) == 0 {
		return nil, errors.New("文档数据不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	result, err := collect.InsertMany(Ctx, documents)
	if err != nil {
		return nil, err
	}

	return result.InsertedIDs, nil
}

/**
 * 删除文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param map[string]interface{} params -------------必传
 * @param bool isBatch ------------------------------必传
 * @return error
 */
func (m *MongoInstance) DeleteDocument(database, collection string, params map[string]interface{}, isBatch bool) error {
	if database == "" {
		return errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return errors.New("集合名称不能为空")
	}
	if len(params) == 0 {
		return errors.New("参数不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	var err error
	var result *mongo.DeleteResult
	if isBatch { //批量删除文档
		result, err = collect.DeleteMany(Ctx, params)
	} else { //删除单个文档
		result, err = collect.DeleteOne(Ctx, params)
	}
	if err != nil {
		return err
	}

	if result.DeletedCount == 0 {
		return errors.New("删除文档失败")
	}

	return nil
}

/**
 * 根据文档ID修改文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param map[string]string documentId --------------必传
 * @param map[string]string params ------------------必传
 * @return error
 */
func (m *MongoInstance) EditDocumentById(database, collection string, documentId map[string]string, params map[string]string) error {
	if database == "" {
		return errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return errors.New("集合名称不能为空")
	}
	if len(documentId) == 0 {
		return errors.New("文档ID不能为空")
	}
	if len(params) == 0 {
		return errors.New("参数不能为空")
	}

	var id interface{}
	var err error

	id = documentId["_id"]

	//如果文档ID是object类型需要进行转换
	if documentId["type"] == "object" {
		id, err = primitive.ObjectIDFromHex(documentId["_id"])
		if err != nil {
			return err
		}
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	result, err := collect.UpdateByID(Ctx, id, bson.D{{"$set", params}})
	if err != nil {
		return err
	}

	if result.MatchedCount == 0 {
		return errors.New("文档不存在")
	}

	if result.ModifiedCount == 0 {
		return errors.New("编辑文档失败")
	}

	return nil
}

/**
 * 编辑文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param map[string]interface{} filters ------------必传
 * @param map[string]string params ------------------必传
 * @param bool isBatch ------------------------------必传
 * @return error
 */
func (m *MongoInstance) EditDocument(database, collection string, filters map[string]interface{}, params map[string]string, isBatch bool) error {
	if database == "" {
		return errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return errors.New("集合名称不能为空")
	}
	if len(filters) == 0 {
		return errors.New("过滤参数不能为空")
	}
	if len(params) == 0 {
		return errors.New("参数不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	var err error
	var result *mongo.UpdateResult
	if isBatch { //批量编辑文档
		_, err = collect.UpdateMany(Ctx, filters, bson.D{{"$set", params}})
	} else { //编辑单个文档
		result, err = collect.UpdateOne(Ctx, filters, bson.D{{"$set", params}})
	}
	if err != nil {
		return err
	}

	if result.MatchedCount == 0 {
		return errors.New("文档不存在")
	}

	if result.ModifiedCount == 0 {
		return errors.New("编辑文档失败")
	}

	return nil
}

/**
 * 查询单条文档
 *
 * @param string database ---------------------------必传
 * @param string collection -------------------------必传
 * @param map[string]string params ------------------必传
 * @return map[string]string, error
 */
func (m *MongoInstance) Row(database, collection string, params map[string]string) (map[string]string, error) {
	if database == "" {
		return nil, errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return nil, errors.New("集合名称不能为空")
	}
	if len(params) == 0 {
		return nil, errors.New("参数不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	var result map[string]string
	sr := collect.FindOne(Ctx, params)
	err := sr.Decode(&result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

/**
 * 查询文档
 *
 * @param string database ------------------------必传
 * @param string collection ----------------------必传
 * @param map[string]string params ---------------必传
 * @param int64 page -----------------------------必传
 * @param int64 pageSize -------------------------必传
 * @param map[string]string sort -----------------必传
 * @return []map[string]string, error
 */
func (m *MongoInstance) GetResultsByParams(database, collection string, params map[string]string, page, pageSize int64, sort map[string]string) ([]map[string]string, error) {
	if database == "" {
		return nil, errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return nil, errors.New("集合名称不能为空")
	}

	var opts options.FindOptions
	if len(sort) == 0 {
		opts.SetSort(sort)
	}

	opts.SetSkip((page - 1) * pageSize)
	opts.SetLimit(pageSize)

	collect := m.mongoClient.Database(database).Collection(collection)
	cursor, err := collect.Find(Ctx, params, &opts)
	if err != nil {
		return nil, err
	}

	defer cursor.Close(Ctx)

	var results []map[string]string
	err = cursor.All(Ctx, &results)
	if err != nil {
		return nil, err
	}

	return results, nil
}

/**
 * 查询文档数量
 *
 * @param string database ---------------------必传
 * @param string collection -------------------必传
 * @param map[string]string params ------------必传
 * @return int64, error
 */
func (m *MongoInstance) GetCountByParams(database, collection string, params map[string]string) (int64, error) {
	if database == "" {
		return 0, errors.New("数据库名称不能为空")
	}
	if collection == "" {
		return 0, errors.New("集合名称不能为空")
	}

	collect := m.mongoClient.Database(database).Collection(collection)
	count, err := collect.CountDocuments(Ctx, params)
	if err != nil {
		return 0, err
	}

	return count, nil
}
